/* DefaultNavigationLevel.java

	Purpose:
		
	Description:
		
	History:
		Fri Aug 24 12:09:26 CST 2018, Created by rudyhuang

Copyright (C) 2018 Potix Corporation. All Rights Reserved.
*/
package org.zkoss.zuti.zul;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import org.zkoss.zuti.event.NavigationEvent;
import org.zkoss.lang.Classes;
import org.zkoss.lang.Strings;
import org.zkoss.util.Pair;
import org.zkoss.zk.ui.Executions;

/**
 * @author rudyhuang
 */
class DefaultNavigationLevel<T> implements NavigationLevel<T>, Serializable {
	private static boolean HAS_ZKBIND = true;

	private final NavigationModel<T> _model;
	private final int _level;
	private NavigationNode<T> _node;
	private NavigationNode<T> _head;
	private Map<String, Object> _context;
	private volatile DefaultNavigationLevel<T> _childLevel;
	private boolean _hasNavigated = false;

	static {
		try {
			Classes.forNameByThread("org.zkoss.bind.BindUtils");
		} catch (ClassNotFoundException e) {
			HAS_ZKBIND = false;
		}
	}

	DefaultNavigationLevel(NavigationModel<T> model, int level) {
		_model = model;
		_level = level;
	}

	void setNavigated(boolean navigated) {
		if (_hasNavigated != navigated) {
			_hasNavigated = navigated;
			if (navigated)
				notify(NavigationEvent.Type.NAVIGATE, this, "current");
		}
	}

	NavigationNode<T> getNode() {
		return _node;
	}

	void setNode(NavigationNode<T> node) {
		if (_node != node) {
			_node = node;
			if (_node != null) {
				NavigationNode<T> child = _node.getChild();
				if (_childLevel != null && child != null) {
					_childLevel.setNode(child);
					_childLevel.setHead(child);
					_childLevel.setNavigated(false);
				}
			}
			if (_hasNavigated)
				notify(NavigationEvent.Type.NAVIGATE, this, "current");
		}
	}

	private void notify(NavigationEvent.Type type, Object bean, String... props) {
		if (Executions.getCurrent() == null)
			return;
		_model.fireEvent(this, type, _node);
		if (HAS_ZKBIND)
			org.zkoss.bind.BindUtils.postNotifyChange(null, null, bean, props);
	}

	NavigationNode<T> getHead() {
		if (_head == null) {
			_head = _node;
		}

		if (_head != null) {
			NavigationNode<T> head = _head;
			do {
				if (head.getLeft() == null) {
					_head = head;
					break;
				}
				head = head.getLeft();
			} while (head != null);
		}
		return _head;
	}

	void setHead(NavigationNode<T> head) {
		_head = head;
	}

	public int getLevel() {
		return _level;
	}

	public Map<String, Object> getContext() {
		return _context;
	}

	public NavigationLevel<T> setContext(Map<String, Object> context) {
		if (this._context != context) {
			this._context = context;
			notify(NavigationEvent.Type.CHANGE_CONTEXT, this, "context");
		}
		return this;
	}

	public String getCurrentKey() {
		if (_hasNavigated)
			return _node != null ? _node.getKey() : null;
		return null;
	}

	public T getCurrent() {
		if (_hasNavigated)
			return _node != null ? _node.getValue() : null;
		return null;
	}

	public NavigationLevel<T> getChild() {
		if (_node == null)
			return null;

		NavigationNode<T> child = _node.getChild();
		if (child != null) {
			if (_childLevel == null) {
				synchronized (this) {
					if (_childLevel == null) {
						_childLevel = new DefaultNavigationLevel<T>(_model, _level + 1);
						_childLevel.setNode(child);
						_childLevel.setHead(child);
					}
				}
			}
			return _childLevel;
		} else
			return null;
	}

	public NavigationLevel<T> navigateTo(String key) {
		if (Strings.isEmpty(key))
			throw new IllegalArgumentException("key cannot be null or empty");
		if (_node == null)
			throw new IllegalArgumentException("the level is empty");

		NavigationNode<T> n = _node;
		do {
			if (key.equals(n.getKey())) {
				setNode(n);
				setNavigated(true);
				return this;
			}
			n = n.getRight();
		} while (n != null);

		n = _node;
		while ((n = n.getLeft()) != null) {
			if (key.equals(n.getKey())) {
				setNode(n);
				setNavigated(true);
				return this;
			}
		}

		throw new IllegalArgumentException("Not found in the level: " + key);
	}

	public Iterator<Pair<String, T>> getItemIterator() {
		return new Iterator<Pair<String, T>>() {
			private NavigationNode<T> n = getHead();

			public boolean hasNext() {
				return n != null;
			}

			public Pair<String, T> next() {
				if (hasNext()) {
					Pair<String, T> result = new Pair<String, T>(n.getKey(), n.getValue());
					n = n.getRight();
					return result;
				}
				throw new NoSuchElementException();
			}

			public void remove() {
				throw new UnsupportedOperationException("readonly");
			}
		};
	}

	public List<Pair<String, T>> getItems() {
		List<Pair<String, T>> result = new ArrayList<Pair<String, T>>();
		Iterator<Pair<String, T>> iter = getItemIterator();
		while (iter.hasNext()) {
			result.add(iter.next());
		}
		return result;
	}

	@Override
	public String toString() {
		final StringBuilder sb = new StringBuilder("DefaultNavigationLevel{");
		sb.append("_level=").append(_level);
		sb.append(", _node=").append(_node);
		sb.append(", _context=").append(_context);
		sb.append('}');
		return sb.toString();
	}
}
